package javango.contrib.hibernate;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.beanutils.ConvertUtilsBean;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;

import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;

import javango.contrib.i18n.I18NProvider;
import javango.db.Manager;
import javango.db.ManagerException;
import javango.db.QuerySet;

public class HibernateManager<T> implements Manager<T> {
	
	private final HibernateUtil hibernateUtil;	
	private final Class<? extends T> model;
	
	@Inject
	public HibernateManager(HibernateUtil hibernateUtil, @Assisted Class<? extends T> model) {
		super();
		this.hibernateUtil = hibernateUtil;
		this.model = model;
	}

	public QuerySet<T> all() throws ManagerException {
		try {
			return new HibernateQuerySet<T>(hibernateUtil, model);
		} catch (Exception e) {
			throw new ManagerException(e);
		}
	}

	public void delete(T object) throws ManagerException {
		try {
			hibernateUtil.getSession().delete(object);
		} catch (HibernateException e) {
			throw new ManagerException(e);
		}

	}

	public QuerySet<T> filter(Map<String, Object> params) throws ManagerException {
		return all().filter(params);
	}
	
	public QuerySet<T> filter(Object params) throws ManagerException {
		return all().filter(params);
	}
	
	public QuerySet<T> filter(String property, Object value) throws ManagerException {
		return all().filter(property, value);
	}
	
	public QuerySet<T> filterByProperty(String propertyA, String propertyB) throws ManagerException {
		return all().filterByProperty(propertyA, propertyB);
	}

	@SuppressWarnings("unchecked")
	public T get(Serializable key) throws ManagerException {
		if (key == null) return null;
		
		Class<?>[] keyClass = getPkClass();
		if (keyClass.length != 1) {
			throw new ManagerException("get(pk) not supported for composite keyed models");
		}		
		if (key.getClass().equals(keyClass[0])) {
			return (T) hibernateUtil.getSession().get(model, key);
		}		
		
//			if (log.isDebugEnabled()) log.debug("Trying to convert input to type " + keyClass[0]);
		
		Object o = new ConvertUtilsBean().convert(key.toString(), keyClass[0]);
		
		return (T)hibernateUtil.getSession().get(model, (Serializable)o);

	}

	public T save(T object) throws ManagerException {
		try {
			hibernateUtil.getSession().saveOrUpdate(object);
		} catch (HibernateException e) {
			throw new ManagerException(e);
		}
		return object;
	}
	
	public T create(T object) throws ManagerException {
		try {
			hibernateUtil.getSession().save(object);
		} catch (HibernateException e) {
			throw new ManagerException(e);
		}
		return object;
	}

	public Serializable getPk(T object) throws ManagerException {
		try {
			return hibernateUtil.getSession().getIdentifier(object);
		} catch (HibernateException e) {
			// TODO Throw something cool here and elsewhere...
			throw new ManagerException(e);
		}
	}

	/**
	 * Returns a the keys Class[] 
	 * @return
	 * @throws DaoException
	 */
	public Class[] getPkClass() throws ManagerException {
		Configuration cfg = hibernateUtil.getConfiguration();
		PersistentClass pclass = cfg.getClassMapping(model.getName());

		if (pclass == null) {
			throw new ManagerException("Unable to find class : "+ model.toString());
		}

		Property componentProperty = pclass.getIdentifierProperty();
		if (componentProperty == null) {
			Component component = pclass.getIdentifierMapper();
			if (component == null) {
				throw new ManagerException("Unable to get pk mapping");
			}
//			if (log.isDebugEnabled()) log.debug(String.format("Found %d keys for model %s", component.getPropertySpan(), model.getName()));

			Class<?>[] classArray = new Class[component.getColumnSpan()];
			Iterator<Property> ite = component.getPropertyIterator();
			int i = 0;
			while (ite.hasNext()) {
				Property p = ite.next();
//				if (log.isDebugEnabled()) log.debug("property name: " + p.getName());
				classArray[i++] = p.getType().getReturnedClass();
			}
			return classArray;
		} else {

			Value value = componentProperty.getValue();
			if (value == null)
				throw new ManagerException("Component value is null");
			else {
//				if (log.isDebugEnabled()) log.debug(String.format("Found simple key for model %s '%s'",	model.getName(), value.getType().getReturnedClass()));
				return new Class[] { value.getType().getReturnedClass() };
			}
		}
	}
	
	/**
	 * Returns a the keys Class[] 
	 * @return
	 * @throws DaoException
	 */
	public String getPkProperty() throws ManagerException {
		Configuration cfg = hibernateUtil.getConfiguration();
		PersistentClass pclass = cfg.getClassMapping(model.getName());

		if (pclass == null) {
			throw new ManagerException("Unable to find class : "
					+ model.toString());
		}

		Property componentProperty = pclass.getIdentifierProperty();
		if (componentProperty == null) {
			throw new UnsupportedOperationException("Multiple primary keys not supported");
//			Component component = pclass.getIdentifierMapper();
//			if (component == null) {
//				throw new DaoException("Unable to get pk mapping");
//			}
//			if (log.isDebugEnabled()) log.debug(String.format("Found %d keys for model %s", component.getPropertySpan(), modelClass.getName()));
//
//			Class<?>[] classArray = new Class[component.getColumnSpan()];
//			Iterator<Property> ite = component.getPropertyIterator();
//			int i = 0;
//			while (ite.hasNext()) {
//				Property p = ite.next();
//				if (log.isDebugEnabled()) log.debug("property name: " + p.getName());
//				classArray[i++] = p.getType().getReturnedClass();
//			}
//			return classArray;
		} else {
			return componentProperty.getName();
		}
	}

}
